/*
 * aPLib compression library  -  the smaller the better :)
 *
 * C depacker
 *
 * Copyright (c) 1998-2014 Joergen Ibsen
 * All Rights Reserved
 *
 * http://www.ibsensoftware.com/
 */

#include "depack.h"

/* internal data structure */
struct APDSTATE {
  const unsigned char *source;
  unsigned char *destination;
  unsigned int tag;
  unsigned int bitcount;
};

static unsigned int aP_getbit(struct APDSTATE *ud)
{
  unsigned int bit;

  /* check if tag is empty */
  if (!ud->bitcount--) {
    /* load next tag */
    ud->tag = *ud->source++;
    ud->bitcount = 7;
  }

  /* shift bit out of tag */
  bit = (ud->tag >> 7) & 0x01;
  ud->tag <<= 1;

  return bit;
}

static unsigned int aP_getgamma(struct APDSTATE *ud)
{
  unsigned int result = 1;

  /* input gamma2-encoded bits */
  do {
    result = (result << 1) + aP_getbit(ud);
  } while (aP_getbit(ud));

  return result;
}

unsigned int aP_depack(const void *source, void *destination)
{
  struct APDSTATE ud;
  unsigned int offs, len, R0, LWM;
  int done;
  int i;

  ud.source = (const unsigned char *) source;
  ud.destination = (unsigned char *) destination;
  ud.bitcount = 0;

  R0 = (unsigned int) -1;
  LWM = 0;
  done = 0;

  /* first byte verbatim */
  *ud.destination++ = *ud.source++;

  /* main decompression loop */
  while (!done) {
    if (aP_getbit(&ud)) {
      if (aP_getbit(&ud)) {
        if (aP_getbit(&ud)) {
          offs = 0;

          for (i = 4; i; i--) {
            offs = (offs << 1) + aP_getbit(&ud);
          }

          if (offs) {
            *ud.destination = *(ud.destination - offs);
            ud.destination++;
          }
          else {
            *ud.destination++ = 0x00;
          }

          LWM = 0;
        }
        else {
          offs = *ud.source++;

          len = 2 + (offs & 0x0001);

          offs >>= 1;

          if (offs) {
            for (; len; len--) {
              *ud.destination = *(ud.destination - offs);
              ud.destination++;
            }
          }
          else {
            done = 1;
          }

          R0 = offs;
          LWM = 1;
        }
      }
      else {
        offs = aP_getgamma(&ud);

        if ((LWM == 0) && (offs == 2)) {
          offs = R0;

          len = aP_getgamma(&ud);

          for (; len; len--) {
            *ud.destination = *(ud.destination - offs);
            ud.destination++;
          }
        }
        else {
          if (LWM == 0) {
            offs -= 3;
          }
          else {
            offs -= 2;
          }

          offs <<= 8;
          offs += *ud.source++;

          len = aP_getgamma(&ud);

          if (offs >= 32000) {
            len++;
          }
          if (offs >= 1280) {
            len++;
          }
          if (offs < 128) {
            len += 2;
          }

          for (; len; len--) {
            *ud.destination = *(ud.destination - offs);
            ud.destination++;
          }

          R0 = offs;
        }

        LWM = 1;
      }
    }
    else {
      *ud.destination++ = *ud.source++;
      LWM = 0;
    }
  }

  return (unsigned int) (ud.destination - (unsigned char *) destination);
}
